package com.lzyg.logger;

import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import static com.lzyg.logger.Utils.DEFAULT_FOLDER_NAME;
import static com.lzyg.logger.Utils.checkNotNull;


/**
 * CustomFormatStrategy简介
 *
 * @author zhengyunguang
 * @date 2018-10-10 20:26:26
 */
public class TxtFormatStrategy implements FormatStrategy {

    private static final String NEW_LINE = System.getProperty("line.separator");
    private static final String NEW_LINE_REPLACEMENT = " <br> ";
    private static final String SEPARATOR = ",";
    private static final String COLON = ":";
    private static final String BRACKET_LEFT = "(";
    private static final String BRACKET_RIGHT = ")";

    @NonNull
    private final Date date;
    @NonNull
    private final SimpleDateFormat dateFormat;
    @NonNull
    private final LogStrategy logStrategy;
    @Nullable
    private final String tag;
    /**
     * showThreadInfo
     */
    boolean showThreadInfo;
    /**
     * withCaller
     */
    boolean withCaller;

    private TxtFormatStrategy(@NonNull Builder builder) {
        checkNotNull(builder);

        date = builder.date;
        dateFormat = builder.dateFormat;
        logStrategy = builder.logStrategy;
        tag = builder.tag;
        showThreadInfo = builder.showThreadInfo;
        withCaller = builder.withCaller;
    }

    @NonNull
    public static Builder newBuilder() {
        return new TxtFormatStrategy.Builder();
    }

    @Override
    public void log(int priority, @Nullable String onceOnlyTag, @NonNull String message) {
        checkNotNull(message);

        String tag = formatTag(onceOnlyTag);

        date.setTime(System.currentTimeMillis());

        StringBuilder builder = new StringBuilder();
        builder.append(dateFormat.format(date));

        // level
        builder.append(SEPARATOR);
        builder.append(Utils.logLevel(priority));

        // tag
        builder.append(SEPARATOR);
        builder.append(tag);
        builder.append(COLON);
        if (withCaller) {
            builder.append(BRACKET_LEFT);
            builder.append("Caller:" + getCaller());
            builder.append(BRACKET_RIGHT);
        }
        if (withCaller && showThreadInfo) {
            builder.append("-");
        }
        if (showThreadInfo) {
            builder.append(BRACKET_LEFT);
            builder.append("Thread:" + Thread.currentThread().getName());
            builder.append(BRACKET_RIGHT);
        }
        // message
        if (message.contains(NEW_LINE)) {
            // a new line would break the CSV format, so we replace it here
            message = message.replaceAll(NEW_LINE, NEW_LINE_REPLACEMENT);
        }
        builder.append(message);

        // new line
        builder.append(NEW_LINE);

        logStrategy.log(priority, tag, builder.toString());
    }

    @Nullable
    private String formatTag(@Nullable String tag) {
        if (TextUtils.isEmpty(tag) && TextUtils.isEmpty(this.tag)) {
            return Utils.DEFAULT_TAG;
        }
        return TextUtils.isEmpty(tag) ? this.tag : tag;
    }


    /**
     * getCaller
     *
     * @return
     */
    private String getCaller() {
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        StackTraceElement callerTrace = null;
        String caller = "";
        for (int i = Utils.MIN_STACK_OFFSET; i < trace.length; i++) {
            StackTraceElement e = trace[i];
            String name = e.getClassName();
            if ((!name.equals(JLogPrinter.class.getName())) && (!name.equals(JLog.class.getName()))) {
                callerTrace = e;
                break;
            }
        }
        if (callerTrace != null) {
            String callerClazzName = callerTrace.getClassName();
            callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
            StringBuilder builder = new StringBuilder();
            builder.append(callerClazzName)
                    .append(".")
                    .append(callerTrace.getMethodName())
                    .append(":")
                    .append(callerTrace.getLineNumber());
            caller = builder.toString();
        }
        return caller;
    }

    public static final class Builder {
        private static final int MAX_BYTES = 500 * 1024; // 500K averages to a 4000 lines per file

        Date date;
        SimpleDateFormat dateFormat;
        LogStrategy logStrategy;
        String tag;
        boolean showThreadInfo = true;
        boolean withCaller = true;

        private Builder() {
        }

        @NonNull
        public Builder date(@Nullable Date val) {
            date = val;
            return this;
        }

        @NonNull
        public Builder dateFormat(@Nullable SimpleDateFormat val) {
            dateFormat = val;
            return this;
        }

        @NonNull
        public Builder logStrategy(@Nullable LogStrategy val) {
            logStrategy = val;
            return this;
        }

        @NonNull
        public Builder showThreadInfo(boolean val) {
            showThreadInfo = val;
            return this;
        }

        @NonNull
        public Builder withCaller(boolean val) {
            withCaller = val;
            return this;
        }

        @NonNull
        public Builder tag(@Nullable String tag) {
            this.tag = tag;
            return this;
        }

        @NonNull
        public TxtFormatStrategy build() {
            if (date == null) {
                date = new Date();
            }
            if (dateFormat == null) {
                dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.UK);
            }
            if (logStrategy == null) {
                String diskPath = Environment.getExternalStorageDirectory().getAbsolutePath();
                String folder = diskPath + File.separatorChar + DEFAULT_FOLDER_NAME;

                HandlerThread ht = new HandlerThread("AndroidFileLogger." + folder);
                ht.start();
                Handler handler = new DiskStrategy.WriteHandler(ht.getLooper(), folder, MAX_BYTES);
                logStrategy = new DiskStrategy(handler);
            }
            return new TxtFormatStrategy(this);
        }
    }
}
